//
// Created by liaohailong on 2024/11/30.
//

#include "CSApp.hpp"


ssize_t rio_readn(int fd, void *usrbuf, size_t n) {
    size_t nleft = n;
    ssize_t nread;
    char *bufp = static_cast<char *>(usrbuf);

    while (nleft > 0) {
        if ((nread = read(fd, bufp, nleft)) < 0) {
            if (errno == EINTR) { /* Interrupted by sig handler return */
                nread = 0; /* and call read() again */
            } else {
                return -1; /* errno set by read() */
            }
        } else if (nread == 0) {
            break; /* EOF */
        }
        nleft -= nread;
        bufp += nread;
    }
    return (ssize_t) (n - nleft); /* Return >=0 */
}

ssize_t rio_writen(int fd, void *usrbuf, size_t n) {
    size_t nleft = n;
    ssize_t nwritten;
    char *bufp = static_cast<char *>(usrbuf);

    while (nleft > 0) {
        if ((nwritten = write(fd, bufp, nleft)) <= 0) {
            if (errno == EINTR) { /* Interrupted by sig handler return */
                nwritten = 0; /* and call write() again */
            } else {
                return -1; /* errno set by write() */
            }
        }
        nleft -= nwritten;
        bufp += nwritten;
    }

    return (ssize_t) n;
}


void rio_readinitb(rio_t *rp, int fd) {
    rp->rio_fd = fd;
    rp->rio_cnt = 0;
    rp->rio_bufptr = rp->rio_buf;
}

ssize_t rio_read(rio_t *rp, void *usrbuf, ssize_t n) {
    long cnt;

    while (rp->rio_cnt <= 0) { /* Refill if buf is empty */
        rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf));
        // rp->rio_cnt:
        // >0: success read client's data
        // <0: client is disconnected
        // ==0: client's EOF no more data
        if (rp->rio_cnt < 0) {
            if (errno == EINTR)  /* Interrupted by sig handler return */
                return -1;
        } else if (rp->rio_cnt == 0) { /* EOF */
            return 0;
        } else
            rp->rio_bufptr = rp->rio_buf; /* Reset buffer ptr*/
    }

    /* Copy min(n,rp->rio_cnt) bytes from internal buf to user buf*/
    cnt = n;
    if (rp->rio_cnt < n) {
        cnt = rp->rio_cnt;
    }
    memcpy(usrbuf, rp->rio_bufptr, cnt);
    rp->rio_bufptr += cnt;
    rp->rio_cnt -= cnt;
    return (ssize_t) cnt;
}

ssize_t rio_readline(int fd, void *usrbuf, size_t maxlen){
    long n, rc;
    char c, *bufp = static_cast<char *>(usrbuf);

    for (n = 1; n < maxlen; n++) {
        if ((rc = read(fd, &c, 1)) == 1) {
            *bufp++ = c;
            if (c == '\n') {
                n++;
                break;
            }
        } else if (rc == 0) {
            if (n == 1)
                return 0;   /* EOF, no data read */
            else
                break;      /* EOF, some data was read */
        } else
            return -1; /* Error */
    }

    *bufp = 0; /* last char on userbuf is NULL */
    return n - 1; /* Return <= maxlen, only read maxlen-1 chars */
}

ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) {
    long n, rc;
    char c, *bufp = static_cast<char *>(usrbuf);

    for (n = 1; n < maxlen; n++) {
        if ((rc = rio_read(rp, &c, 1)) == 1) {
            *bufp++ = c;
            if (c == '\n') {
                n++;
                break;
            }
        } else if (rc == 0) {
            if (n == 1)
                return 0;   /* EOF, no data read */
            else
                break;      /* EOF, some data was read */
        } else
            return -1; /* Error */
    }

    *bufp = 0; /* last char on userbuf is NULL */
    return n - 1; /* Return <= maxlen, only read maxlen-1 chars */
}

ssize_t rio_readnb(rio_t *rp, void *usrbuf, ssize_t n) {
    ssize_t nleft = n;
    ssize_t nread;
    char *bufp = static_cast<char *>(usrbuf);

    while (nleft > 0) {
        if ((nread = rio_read(rp, bufp, nleft)) < 0) {
            return -1;                  /* errno set by read() */
        } else if (nread == 0) {
            break;                      /* EOF */
        }
        nleft -= nread;
        bufp += nread;
    }
    return (ssize_t) (n - nleft); /* Return >= 0 */
}

int open_clientfd(const char *hostname, const char *port) {
    int clientfd;
    struct addrinfo hints{}, *listp, *p;

    /* Get a list of potential server list*/
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM; // Open a connection
    hints.ai_flags = AI_NUMERICSERV; // ... using a numeric port arg
    hints.ai_flags |= AI_ADDRCONFIG; // Recommended for connections
    int errcode = getaddrinfo(hostname, port, &hints, &listp);

    if (errcode != 0) {
        // 可以通过 gai_strerror 拿到错误信息打印出来
//        const char* err_str = gai_strerror(errcode);
// 在 netdb.h 中定义了错误码
#define    EAI_ADDRFAMILY     1    /* address family for hostname not supported */
#define    EAI_AGAIN     2    /* temporary failure in name resolution */
#define    EAI_BADFLAGS     3    /* invalid value for ai_flags */
#define    EAI_FAIL     4    /* non-recoverable failure in name resolution */
#define    EAI_FAMILY     5    /* ai_family not supported */
#define    EAI_MEMORY     6    /* memory allocation failure */
#define    EAI_NODATA     7    /* no address associated with hostname */
#define    EAI_NONAME     8    /* hostname nor servname provided, or not known */
#define    EAI_SERVICE     9    /* servname not supported for ai_socktype */
#define    EAI_SOCKTYPE    10    /* ai_socktype not supported */
#define    EAI_SYSTEM    11    /* system error returned in errno */
#define    EAI_BADHINTS    12    /* invalid value for hints */
#define    EAI_PROTOCOL    13    /* resolved protocol is unknown */
#define    EAI_OVERFLOW    14    /* argument buffer overflow */
#define    EAI_MAX        15
        return -1;
    }


    // walk the list for one that we can successfully connect to
    for (p = listp; p; p = p->ai_next) {
        // Create a socket describtor
        if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
            continue; // socket failed, try the next

        // Connect to the server
        if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1)
            break; // Success
        close(clientfd);
    }

    // clean up
    freeaddrinfo(listp);
    if (!p) // All connections failed
    {
        return -1;
    }

    return clientfd;
}

int open_listenfd(const char *port) {
    struct addrinfo hints{}, *listp, *p;
    int listenfd, optval = 1;

    // Get a list of potential server address
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM; // Accept connections
    hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; // ... on any IP address
    hints.ai_flags |= AI_NUMERICSERV; // ... using port number
    int errcode = getaddrinfo(nullptr, port, &hints, &listp);

    if (errcode != 0) {
        return -1;
    }

    // Wall the list for one that we can bind to
    for (p = listp; p; p = p->ai_next) {
        // Create a socket descriptor
        if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
            continue; // Socket failed, try the next

        // Eliminates "Address already in use" error for bind
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *) &optval, sizeof(int));

        // setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, (const void *)&optval, sizeof(int));

        if (bind(listenfd, p->ai_addr, p->ai_addrlen) != -1) {
            break; // Success
        }
        close(listenfd); // Bind failed, try the next
    }


    // clean up
    freeaddrinfo(listp);
    if (!p) // No address worked
    {
        return -1;
    }

    // Make it a listening socket ready to accept connection requests
    if (listen(listenfd, LISTENQ) < 0) {
        close(listenfd);
        return -1;
    }

    return listenfd;
}

int open_listen_udp_fd(const char *port) {
    struct addrinfo hints{}, *listp, *p;
    int listenfd, optval = 1;

    // Get a list of potential server address
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_DGRAM; // SOCK_DGRAM: UDP
    hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; // ... on any IP address
    hints.ai_flags |= AI_NUMERICSERV; // ... using port number
    int errcode = getaddrinfo(nullptr, port, &hints, &listp);

    if (errcode != 0) {
        return -1;
    }

    // Wall the list for one that we can bind to
    for (p = listp; p; p = p->ai_next) {
        // Create a socket descriptor
        if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
            continue; // Socket failed, try the next

        // Eliminates "Address already in use" error for bind
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *) &optval, sizeof(int));

        // setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, (const void *)&optval, sizeof(int));

        if (bind(listenfd, p->ai_addr, p->ai_addrlen) != -1) {
            break; // Success
        }
        close(listenfd); // Bind failed, try the next
    }


    // clean up
    freeaddrinfo(listp);
    if (!p) // No address worked
    {
        return -1;
    }

    return listenfd;
}